﻿/*
 * Twipler - Twitter Web Client with features.
 *  
 *  Copyright (C) 2009, 2010. Ian Quigley
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details. 
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Xml;

using Twipler.App.Render.Tree;
using Twipler.App.Parse.Code;

namespace Twipler.App.Parse.Html.Consumers
{
    public class ConsumerAttribute  : Attribute
    {
        public string OpenTag;
        public string CloseTag;
        public string[] OptionalTags;

        public Type Parameters;
    }

    public abstract class TemplateParameters
    {}
    
    public abstract class TemplateConsumer
    {
        public abstract AbstractItem Process(Template template, TemplateConsumer parent);

        protected Dictionary<string, object> Arguments;
        
        public TemplateParameters Parameters = null;

        protected static string OpenTag;
        protected static string CloseTag;
        protected static string[] OptionalTags;

        protected AbstractItem Error(Template template, string message)
        {
            int index = template.Index;
            template.ConsumeToEnd();
            return ParseErrorItem.Create(message, index);
        }

        protected void ReadArguments(Template template)
        {
            if (Parameters == null)
                return;

            TokenParser parser = new TokenParser(template);

            bool isDynamic = false;

            List<string> definedArguments = new List<string>();

            while (!parser.IsEmpty)
            {
                parser.SkipWhiteSpace();
                if (parser.Peek(Template.EndMarker, false))
                    break;
                
                string itemName = parser.GetArgumentName();
                if (parser.HasErrors)
                    break;

                FieldInfo field = (from fields in Parameters.GetType().GetFields()
                                   where String.Compare(fields.Name, itemName, StringComparison.OrdinalIgnoreCase) == 0
                                   select fields).FirstOrDefault();

                if (field == null)
                {
                    // Does the parameter define space for a dynamically built KeyValuePair list?
                    field = (from fields in Parameters.GetType().GetFields()
                             where fields.GetCustomAttributes(typeof(HtmlArgumentDynamicAttribute), true).Count()  == 1
                             select fields).FirstOrDefault();

                    isDynamic = true;
                }

                if (field == null)
                {
                    parser.AddError("Unexpected argument " + itemName);
                    break;
                }

                TokenParser.Result token = parser.NextValue(field);

                if (parser.HasErrors)
                    break;

                if (isDynamic)
                {
                    List<KeyValuePair<string, object>> list = (List<KeyValuePair<string, object>>)field.GetValue(Parameters);
                    if (list == null)
                    {
                        list = new List<KeyValuePair<string, object>>();
                        field.SetValue(Parameters, list);
                    }

                    list.Add(new KeyValuePair<string, object>(itemName, token.Value));
                    isDynamic = false;
                }
                else
                {
                    field.SetValue(Parameters, token.Value);
                    definedArguments.Add(itemName.ToLower());
                }
            }

            // Verify that Non-Optional parameters were set.
            foreach (FieldInfo fi in Parameters.GetType().GetFields())
            {
                HtmlArgumentAttribute[] attributes =
                    (HtmlArgumentAttribute[]) fi.GetCustomAttributes(typeof (HtmlArgumentAttribute), false);

                if ((attributes == null) || (attributes.Length == 0) || (attributes[0].Optional))
                    continue;

                if (definedArguments.Contains(fi.Name.ToLower()) == false)
                    parser.AddError(fi.Name + " is not an optional argument and was not defined");
            }

            template.Digest(parser);
        }

		
    }

    public static class TokenParserExtender
    {
        public static string GetArgumentName(this TokenParser parser)
        {
            TokenParser.Result token = parser.Next(TokenParser.TokenType.ItemName);

            if ((token is TokenParser.ResultEmpty) || (token is TokenParser.ResultError))
            {
                parser.AddError(token is TokenParser.ResultEmpty
                           ? "Unexpected end of string"
                           : ((TokenParser.ResultError)token).Message);

                return null;
            }

            string argumentName = token.Value as string;
            if (string.IsNullOrEmpty(argumentName))
            {
                parser.AddError("Missing or invalid argument name");
                return null;
            }

            if (!parser.SkipCharIgnoreWhitespace('='))
            {
                parser.AddError("Missing = after variable " + argumentName);
                return null;
            }

            return argumentName;
        }

        public static TokenParser.Result NextValue(this TokenParser parser, FieldInfo field)
        {
            TokenParser.TokenType parseAttributeType = GetParseAttributeType(field);

            TokenParser.Result token = parser.Next(parseAttributeType);

            if ((token is TokenParser.ResultEmpty) || (token is TokenParser.ResultError))
            {
                parser.AddError((token is TokenParser.ResultEmpty)
                           ? "Unexpected end of string"
                           : ((TokenParser.ResultError)token).Message);

            }
            else
            {
                // Ignore type checking for dynamic attributes
                if (field.GetCustomAttributes(typeof(HtmlArgumentDynamicAttribute), true).Count() == 0)
                {
                
                    if ((field.FieldType.BaseType == typeof (Enum)) && (token.Value is String))
                    {
                        if (Enum.IsDefined(field.FieldType, token.Value))
                            token.Value = Enum.Parse(field.FieldType, (string) token.Value);
                        else
                            parser.AddError(string.Format("Argument {0} of type {1} does not include value {2}.",
                                                          field.Name, field.FieldType, token.Value.GetType()));
                    }

                
                    if (field.FieldType.IsAssignableFrom(token.Value.GetType()) == false)
                        parser.AddError(string.Format("Argument {0} of type {1} cannot be assigned to type {2}.",
                                                      field.Name, field.FieldType, token.Value.GetType()));
                }
            }
            return token;
        }

        private static TokenParser.TokenType GetParseAttributeType(FieldInfo field)
        {
            HtmlArgumentAttribute[] attributes = (HtmlArgumentAttribute[])field.GetCustomAttributes(typeof(HtmlArgumentAttribute), false);

            if ((attributes == null) || (attributes.Length == 0))
                return TokenParser.TokenType.Any;

            return attributes[0].TokenType;
        }
    }
}